package main

import (
	"context"
	"fmt"
	"strings"
	"time"
)

func ctxGoThrough(ctx context.Context, fn1 func(), fn2 func()) {

	for {
		select {
		case <-ctx.Done():
			fn2()
			return
		default:
			fn1()
			return
		}
	}
}

func makeFakeHttpRequest(ctx context.Context, url string) (string, error) {

	ch := make(chan string)

	go func(ctx context.Context) {
		fmt.Printf("[%v]request (url=%s) start...\n", time.Now().Unix(), url)
		time.Sleep(time.Second * 2)
		ctxGoThrough(ctx, func() {
			fmt.Printf("[%d]request (url=%s) end... %#v\n", time.Now().Unix(), url, ch)
			ch <- "OK!"
		}, func() {
			fmt.Printf("request outer context done\n")
		})
	}(ctx)

	for {
		select {
		case <-ctx.Done():
			fmt.Printf("request outer context done\n")
			return "", fmt.Errorf("out ctx err: %s", ctx.Err().Error())
		case data := <-ch:
			return data, nil
		}
	}
}

// 串行调用测试
func ServiceFuncWithContext(ctx context.Context) (string, error) {

	var data []string

	var (
		url  string
		resp string
		err  error
	)

	url = "https://www.baidu.com"
	ctx1, cancel1 := context.WithTimeout(ctx, time.Second*3)
	defer cancel1()
	resp, err = makeFakeHttpRequest(ctx1, url)
	if err != nil {
		return "", fmt.Errorf("call url=%s failed: %s", url, err.Error())
	} else {
		data = append(data, strings.Join([]string{url, resp}, ": "))
	}

	url = "https://qq.com"
	ctx2, cancel2 := context.WithTimeout(ctx, time.Second*3)
	// ctx2, cancel2 := context.WithTimeout(ctx, time.Second)
	defer cancel2()
	resp, err = makeFakeHttpRequest(ctx2, url)
	if err != nil {
		return "", fmt.Errorf("call url=%s failed: %s", url, err.Error())
	} else {
		data = append(data, strings.Join([]string{url, resp}, ": "))
	}

	return strings.Join(data, "; "), nil
}

// 协程调用
func ServiceFuncWithContext2(ctx context.Context) (string, error) {

	ctx, cancel := context.WithTimeout(ctx, time.Second*3)
	defer cancel()

	urls := []string{"https://www.baidu.com", "https://qq.com"}
	type requestResult struct {
		resp string
		err  error
	}
	chanRes := make(chan *requestResult, len(urls))
	for _, url := range urls {
		go func(ctx context.Context, url string) {
			resp, err := makeFakeHttpRequest(ctx, url)
			fmt.Printf("return request result for url=%s\n", url)
			if err != nil {
				chanRes <- &requestResult{
					resp: "",
					err:  err,
				}
			} else {
				chanRes <- &requestResult{
					resp: resp,
					err:  nil,
				}
			}
		}(ctx, url)
	}

	data := []string{}
	n := len(urls)
	for {
		select {
		case res := <-chanRes:
			n--
			data = append(data, res.resp)
			if n == 0 {
				return strings.Join(data, "; "), nil
			}
		case <-ctx.Done():
			fmt.Printf("service outer context done\n")
			cancel()
			return "", fmt.Errorf("outer context done: %s", ctx.Err())
		}
	}
}

func main() {

	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		defer cancel()
		go func() {
			time.Sleep(time.Second * 1)
			cancel()
		}()

		// data, err := ServiceFuncWithContext(ctx)
		data, err := ServiceFuncWithContext2(ctx)
		if err != nil {
			fmt.Printf("[%v]call service func failed. error: %s\n", time.Now().Unix(), err.Error())
			return
		}
		fmt.Printf("[%v]call service succeed. data: %s\n", time.Now().Unix(), data)
	}()

	for {
		time.Sleep(time.Second)
	}
}
